package com.katesoft.scale4j.web.model;

import com.katesoft.scale4j.persistent.model.unified.AbstractPersistentEntity;
import com.katesoft.scale4j.web.model.embedded.Account;
import com.katesoft.scale4j.web.model.embedded.Person;
import com.katesoft.scale4j.web.model.embedded.UserPreferences;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.envers.Audited;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Classical implementation of User entity.
 *
 * @author kate2007
 */
@SuppressWarnings("serial")
@Entity
@Indexed
@Audited
@Table(name = "t_user")
@Access(AccessType.FIELD)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@org.hibernate.annotations.Entity(dynamicUpdate = true, mutable = true)
@NamedQueries(value = {@NamedQuery(name = User.QUERY_FIND_BY_LOGIN,
                                   query = "select u from User u where u." + User.PROP_LOGIN + " = :username")})
public class User extends AbstractPersistentEntity implements UserDetails, CredentialsContainer
{
    // queries
    public static final String QUERY_FIND_BY_LOGIN = "user.find_by_login";
    // properties
    public static final String PROP_LOGIN = "login";
    public static final String PROP_EMAIL = "email";
    // ***********************************************************************
    // ************************ persistent fields ****************************
    // ***********************************************************************
    @Column(name = "login", unique = true, nullable = false, length = 100)
    @Field(index = Index.TOKENIZED, store = Store.NO)
    private String login;
    @Column(name = "password", nullable = false, length = 255)
    private String password;
    @Column(name = "e_mail", unique = true, length = 255)
    @Field(index = Index.TOKENIZED, store = Store.NO)
    private String email;
    @Column(name = "open_id", length = 100)
    private String openId;
    @Embedded
    @IndexedEmbedded
    private Person person;
    @Embedded
    private Account account;
    @Embedded
    private UserPreferences userPreferences;
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "t_user_roles", joinColumns = @JoinColumn(name = "user_id"),
               inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<Role>();
    // ***********************************************************************
    // ************************* (getters+setters) ***************************
    // ***********************************************************************

    public Person getPerson()
    {
        if (person == null) {
            person = new Person();
        }
        return person;
    }

    public Account getAccount()
    {
        if (account == null) {
            account = new Account();
        }
        return account;
    }

    public String getLogin()
    {
        return login;
    }

    public void setLogin(final String login)
    {
        this.login = login;
    }

    @Override
    public String getPassword()
    {
        return password;
    }

    public void setPassword(final String password)
    {
        this.password = password;
    }

    public String getEmail()
    {
        return email;
    }

    public void setEmail(final String EMail)
    {
        email = EMail;
    }

    public final String getOpenId()
    {
        return openId;
    }

    public final void setOpenId(final String openId)
    {
        this.openId = openId;
    }

    public Set<Role> getRoles()
    {
        if (roles == null) {
            roles = new HashSet<Role>();
        }
        return Collections.checkedSet(roles, Role.class);
    }

    public void setRoles(final Set<Role> roles)
    {
        this.roles = roles;
    }

    public UserPreferences getUserPreferences()
    {
        if (userPreferences == null) {
            userPreferences = new UserPreferences();
        }
        return userPreferences;
    }

    public void setUserPreferences(final UserPreferences userPreferences)
    {
        this.userPreferences = userPreferences;
    }
    // ================ spring security =========================

    @Override
    @Transient
    /**
     * @see org.springframework.security.userdetails.UserDetails#getAuthorities()
     * @return GrantedAuthority[] an array of roles.
     */
    public Collection<GrantedAuthority> getAuthorities()
    {
        final Set<Role> roles = getRoles();
        final Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(roles.size());
        for (final Role role : roles) {
            authorities.add(new GrantedAuthorityImpl(role.getRole()));
        }
        return authorities;
    }

    @Transient
    @Override
    public String getUsername()
    {
        return getLogin();
    }

    @Transient
    @Override
    public boolean isAccountNonExpired()
    {
        return !getAccount().isAccountExpired();
    }

    @Transient
    @Override
    public boolean isAccountNonLocked()
    {
        return !getAccount().isAccountLocked();
    }

    @Transient
    @Override
    public boolean isCredentialsNonExpired()
    {
        return !getAccount().isCredentialsExpired();
    }

    @Transient
    @Override
    public boolean isEnabled()
    {
        return getAccount().isEnabled();
    }

    @Override
    public void eraseCredentials()
    {
        password = null;
    }
}
